agora inbox for [email protected]
help / color / mirror / Atom feedmissing tags
274+ messages / 5 participants
[nested] [flat]
* missing tags
@ 2010-10-02 11:36 Andrew Dunstan <[email protected]>
2010-10-02 12:02 ` Re: missing tags Magnus Hagander <[email protected]>
0 siblings, 1 reply; 274+ messages in thread
From: Andrew Dunstan @ 2010-10-02 11:36 UTC (permalink / raw)
To: Marc G. Fournier <[email protected]>; pgsql-hackers
There are commit messages from about 22 hours ago that say that the
upcoming releases have been tagged, but I don't see the corresponding
tags when I list out the tags in my repo (and there has been a commit
since then).
I'm not sure where the release procedures are documented - I couldn't
find anything on the wiki.
At the least this is confusing. If it wasn't intended that releases
would be tagged yet, then the commit message should have read something
else, ISTM.
cheers
andrew
^ permalink raw reply [nested|flat] 274+ messages in thread
* Re: missing tags
2010-10-02 11:36 missing tags Andrew Dunstan <[email protected]>
@ 2010-10-02 12:02 ` Magnus Hagander <[email protected]>
2010-10-02 14:08 ` Re: missing tags Tom Lane <[email protected]>
0 siblings, 1 reply; 274+ messages in thread
From: Magnus Hagander @ 2010-10-02 12:02 UTC (permalink / raw)
To: Andrew Dunstan <[email protected]>; +Cc: Marc G. Fournier <[email protected]>; pgsql-hackers
On Sat, Oct 2, 2010 at 13:36, Andrew Dunstan <[email protected]> wrote:
> There are commit messages from about 22 hours ago that say that the
> upcoming releases have been tagged, but I don't see the corresponding tags
> when I list out the tags in my repo (and there has been a commit since
> then).
>
> I'm not sure where the release procedures are documented - I couldn't find
> anything on the wiki.
>
> At the least this is confusing. If it wasn't intended that releases would be
> tagged yet, then the commit message should have read something else, ISTM.
This was intentional - to wait with the tags until the tarballs have
been verified *and published*, so we don't end up having to move the
tags later if we find a last-minute problem.
I think the confusion is from the use of "tagged" in the commit
message - it doesn't have anyting to do with tagging from a git/scm
perspective, it's just updating the version tags in the source which
is a completely different operation.
--
Magnus Hagander
Me: http://www.hagander.net/
Work: http://www.redpill-linpro.com/
^ permalink raw reply [nested|flat] 274+ messages in thread
* Re: missing tags
2010-10-02 11:36 missing tags Andrew Dunstan <[email protected]>
2010-10-02 12:02 ` Re: missing tags Magnus Hagander <[email protected]>
@ 2010-10-02 14:08 ` Tom Lane <[email protected]>
2010-10-02 14:45 ` Re: missing tags Magnus Hagander <[email protected]>
0 siblings, 1 reply; 274+ messages in thread
From: Tom Lane @ 2010-10-02 14:08 UTC (permalink / raw)
To: Magnus Hagander <[email protected]>; +Cc: Andrew Dunstan <[email protected]>; Marc G. Fournier <[email protected]>; pgsql-hackers
Magnus Hagander <[email protected]> writes:
> On Sat, Oct 2, 2010 at 13:36, Andrew Dunstan <[email protected]> wrote:
>> There are commit messages from about 22 hours ago that say that the
> This was intentional - to wait with the tags until the tarballs have
> been verified *and published*, so we don't end up having to move the
> tags later if we find a last-minute problem.
Yeah. Given that moving/removing tags in Git is next to impossible,
we decided not to follow our old practice of applying tags immediately
after the version-stamping commits anymore. We'll tag later once we're
sure a respin isn't going to happen. It's easy and reliable to place a
tag on a past commit (unlike in CVS), so that should work fine. This
decision already paid benefits: if Marc had already pushed a 9.0.1 tag
when he discovered the docs didn't build, we'd have had a problem.
Just for the record, I believe this is where the tags will go, barring
we find another need to respin:
git tag REL9_0_1 a0ccae7ed41201251c0da51a91b84eb9d13c4ab1
git tag REL8_4_5 2f76a4b5a6bcefa03f5a4d377cfb449fd5d95185
git tag REL8_3_12 e32229adfa0769281b0650c8f999d54079c97661
git tag REL8_2_18 61318f38c0999e828d34230c3ea57eb24c49ceba
git tag REL8_1_22 d6d7926cd198cd8d42b0f2d40cbd8f876be9f278
git tag REL8_0_26 c689dacd6deba81c742ec35b2b792fba47db1ffc
git tag REL7_4_30 fd7fdbc88be20f5fc8789254b18902116f88b5fe
Hopefully that agrees with Marc's notes.
> I think the confusion is from the use of "tagged" in the commit
> message
Possibly Marc should adopt the habit of making the commit messages read
like "Stamp 9.0.2", rather than "Tag".
regards, tom lane
^ permalink raw reply [nested|flat] 274+ messages in thread
* Re: missing tags
2010-10-02 11:36 missing tags Andrew Dunstan <[email protected]>
2010-10-02 12:02 ` Re: missing tags Magnus Hagander <[email protected]>
2010-10-02 14:08 ` Re: missing tags Tom Lane <[email protected]>
@ 2010-10-02 14:45 ` Magnus Hagander <[email protected]>
2010-10-02 15:09 ` Re: missing tags Andrew Dunstan <[email protected]>
2010-10-02 18:48 ` Re: missing tags Marc G. Fournier <[email protected]>
0 siblings, 2 replies; 274+ messages in thread
From: Magnus Hagander @ 2010-10-02 14:45 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Andrew Dunstan <[email protected]>; Marc G. Fournier <[email protected]>; pgsql-hackers
On Sat, Oct 2, 2010 at 16:08, Tom Lane <[email protected]> wrote:
> Magnus Hagander <[email protected]> writes:
>> On Sat, Oct 2, 2010 at 13:36, Andrew Dunstan <[email protected]> wrote:
>> I think the confusion is from the use of "tagged" in the commit
>> message
>
> Possibly Marc should adopt the habit of making the commit messages read
> like "Stamp 9.0.2", rather than "Tag".
+1, that sounds like a good idea.
--
Magnus Hagander
Me: http://www.hagander.net/
Work: http://www.redpill-linpro.com/
^ permalink raw reply [nested|flat] 274+ messages in thread
* Re: missing tags
2010-10-02 11:36 missing tags Andrew Dunstan <[email protected]>
2010-10-02 12:02 ` Re: missing tags Magnus Hagander <[email protected]>
2010-10-02 14:08 ` Re: missing tags Tom Lane <[email protected]>
2010-10-02 14:45 ` Re: missing tags Magnus Hagander <[email protected]>
@ 2010-10-02 15:09 ` Andrew Dunstan <[email protected]>
1 sibling, 0 replies; 274+ messages in thread
From: Andrew Dunstan @ 2010-10-02 15:09 UTC (permalink / raw)
To: Magnus Hagander <[email protected]>; +Cc: Tom Lane <[email protected]>; Marc G. Fournier <[email protected]>; pgsql-hackers
On 10/02/2010 10:45 AM, Magnus Hagander wrote:
> On Sat, Oct 2, 2010 at 16:08, Tom Lane<[email protected]> wrote:
>> Magnus Hagander<[email protected]> writes:
>>> On Sat, Oct 2, 2010 at 13:36, Andrew Dunstan<[email protected]> wrote:
>>> I think the confusion is from the use of "tagged" in the commit
>>> message
>> Possibly Marc should adopt the habit of making the commit messages read
>> like "Stamp 9.0.2", rather than "Tag".
> +1, that sounds like a good idea.
>
Yeah, I agree.
Where are the procedures actually documented?
cheers
andrew
^ permalink raw reply [nested|flat] 274+ messages in thread
* Re: missing tags
2010-10-02 11:36 missing tags Andrew Dunstan <[email protected]>
2010-10-02 12:02 ` Re: missing tags Magnus Hagander <[email protected]>
2010-10-02 14:08 ` Re: missing tags Tom Lane <[email protected]>
2010-10-02 14:45 ` Re: missing tags Magnus Hagander <[email protected]>
@ 2010-10-02 18:48 ` Marc G. Fournier <[email protected]>
1 sibling, 0 replies; 274+ messages in thread
From: Marc G. Fournier @ 2010-10-02 18:48 UTC (permalink / raw)
To: Magnus Hagander <[email protected]>; +Cc: Tom Lane <[email protected]>; Andrew Dunstan <[email protected]>; pgsql-hackers
Agreed, I thought of that when Andrew sent the original ...
On Sat, 2 Oct 2010, Magnus Hagander wrote:
> On Sat, Oct 2, 2010 at 16:08, Tom Lane <[email protected]> wrote:
>> Magnus Hagander <[email protected]> writes:
>>> On Sat, Oct 2, 2010 at 13:36, Andrew Dunstan <[email protected]> wrote:
>>> I think the confusion is from the use of "tagged" in the commit
>>> message
>>
>> Possibly Marc should adopt the habit of making the commit messages read
>> like "Stamp 9.0.2", rather than "Tag".
>
> +1, that sounds like a good idea.
>
>
> --
> Magnus Hagander
> Me: http://www.hagander.net/
> Work: http://www.redpill-linpro.com/
>
----
Marc G. Fournier Hub.Org Hosting Solutions S.A.
[email protected] http://www.hub.org
Yahoo:yscrappy Skype: hub.org ICQ:7615664 MSN:[email protected]
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions
@ 2025-08-29 14:12 Nikolay Shaplov <[email protected]>
0 siblings, 0 replies; 274+ messages in thread
From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw)
Introduce trenary reloption as a replacement for current `vacuum_truncate`
implementation. Remove `vacuum_truncate_set` additional flag and using
`TRENARY_UNSET` value instead.
---
src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++-----
src/backend/commands/vacuum.c | 4 +-
src/include/access/reloptions.h | 26 ++---
src/include/c.h | 16 +++
src/include/utils/rel.h | 3 +-
5 files changed, 135 insertions(+), 43 deletions(-)
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..24662d277c8 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -40,9 +40,9 @@
*
* To add an option:
*
- * (i) decide on a type (bool, integer, real, enum, string), name, default
- * value, upper and lower bounds (if applicable); for strings, consider a
- * validation routine.
+ * (i) decide on a type (bool, trenary, integer, real, enum, string), name,
+ * default value, upper and lower bounds (if applicable); for strings,
+ * consider a validation routine.
* (ii) add a record below (or use add_<type>_reloption).
* (iii) add it to the appropriate options struct (perhaps StdRdOptions)
* (iv) add it to the appropriate handling routine (perhaps
@@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] =
},
false
},
- {
- {
- "vacuum_truncate",
- "Enables vacuum to truncate empty pages at the end of this table",
- RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
- ShareUpdateExclusiveLock
- },
- true
- },
{
{
"deduplicate_items",
@@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] =
{{NULL}}
};
+static relopt_trenary trenaryRelOpts[] =
+{
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ TRENARY_UNSET
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
static relopt_int intRelOpts[] =
{
{
@@ -600,6 +606,13 @@ initialize_reloptions(void)
boolRelOpts[i].gen.lockmode));
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode,
+ trenaryRelOpts[i].gen.lockmode));
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
@@ -640,6 +653,14 @@ initialize_reloptions(void)
j++;
}
+ for (i = 0; trenaryRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &trenaryRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_TRENARY;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
for (i = 0; intRelOpts[i].gen.name; i++)
{
relOpts[j] = &intRelOpts[i].gen;
@@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
case RELOPT_TYPE_BOOL:
size = sizeof(relopt_bool);
break;
+ case RELOPT_TYPE_TRENARY:
+ size = sizeof(relopt_trenary);
+ break;
case RELOPT_TYPE_INT:
size = sizeof(relopt_int);
break;
@@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name,
add_local_reloption(relopts, (relopt_gen *) newoption, offset);
}
+/*
+ * init_trenary_reloption
+ * Allocate and initialize a new trenary reloption
+ */
+static relopt_trenary *
+init_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption;
+
+ newoption = (relopt_trenary *) allocate_reloption(kinds,
+ RELOPT_TYPE_TRENARY, name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_trenary_reloption
+ * Add a new trenary reloption
+ */
+void
+add_trenary_reloption(bits32 kinds, const char *name, const char *desc,
+ trenary default_val, LOCKMODE lockmode)
+{
+ relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_trenary_reloption
+ * Add a new trenary local reloption
+ *
+ * 'offset' is offset of trenary-typed field.
+ */
+void
+add_local_trenary_reloption(local_relopts *relopts, const char *name,
+ const char *desc, trenary default_val,
+ int offset)
+{
+ relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
/*
* init_real_reloption
@@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
option->gen->name, value)));
}
break;
+ case RELOPT_TYPE_TRENARY:
+ {
+ bool b;
+ parsed = parse_bool(value, &b);
+ option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE;
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for trenary option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
case RELOPT_TYPE_INT:
{
relopt_int *optint = (relopt_int *) option->gen;
@@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize,
char *itempos = ((char *) rdopts) + elems[j].offset;
char *string_val;
- /*
- * If isset_offset is provided, store whether the reloption is
- * set there.
- */
- if (elems[j].isset_offset > 0)
- {
- char *setpos = ((char *) rdopts) + elems[j].isset_offset;
-
- *(bool *) setpos = options[i].isset;
- }
-
switch (options[i].gen->type)
{
case RELOPT_TYPE_BOOL:
@@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize,
options[i].values.bool_val :
((relopt_bool *) options[i].gen)->default_val;
break;
+ case RELOPT_TYPE_TRENARY:
+ *(trenary *) itempos = options[i].isset ?
+ options[i].values.trenary_val :
+ ((relopt_trenary *) options[i].gen)->default_val;
+ break;
case RELOPT_TYPE_INT:
*(int *) itempos = options[i].isset ?
options[i].values.int_val :
@@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
offsetof(StdRdOptions, parallel_workers)},
{"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
offsetof(StdRdOptions, vacuum_index_cleanup)},
- {"vacuum_truncate", RELOPT_TYPE_BOOL,
- offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)},
+ {"vacuum_truncate", RELOPT_TYPE_TRENARY,
+ offsetof(StdRdOptions, vacuum_truncate)},
{"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)}
};
@@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
elems[i].optname = opt->option->name;
elems[i].opttype = opt->option->type;
elems[i].offset = opt->offset;
- elems[i].isset_offset = 0; /* not supported for local relopts yet */
i++;
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 733ef40ae7c..7b96c7f9a80 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
{
StdRdOptions *opts = (StdRdOptions *) rel->rd_options;
- if (opts && opts->vacuum_truncate_set)
+ if (opts && opts->vacuum_truncate != TRENARY_UNSET)
{
- if (opts->vacuum_truncate)
+ if (opts->vacuum_truncate == TRENARY_TRUE)
params.truncate = VACOPTVALUE_ENABLED;
else
params.truncate = VACOPTVALUE_DISABLED;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..1a6717ed213 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -29,6 +29,7 @@
typedef enum relopt_type
{
RELOPT_TYPE_BOOL,
+ RELOPT_TYPE_TRENARY, /* on, off, unset */
RELOPT_TYPE_INT,
RELOPT_TYPE_REAL,
RELOPT_TYPE_ENUM,
@@ -80,6 +81,7 @@ typedef struct relopt_value
union
{
bool bool_val;
+ trenary trenary_val;
int int_val;
double real_val;
int enum_val;
@@ -94,6 +96,12 @@ typedef struct relopt_bool
bool default_val;
} relopt_bool;
+typedef struct relopt_rernary
+{
+ relopt_gen gen;
+ int default_val;
+} relopt_trenary;
+
typedef struct relopt_int
{
relopt_gen gen;
@@ -152,19 +160,6 @@ typedef struct
const char *optname; /* option's name */
relopt_type opttype; /* option's datatype */
int offset; /* offset of field in result struct */
-
- /*
- * isset_offset is an optional offset of a field in the result struct that
- * stores whether the option is explicitly set for the relation or if it
- * just picked up the default value. In most cases, this can be
- * accomplished by giving the reloption a special out-of-range default
- * value (e.g., some integer reloptions use -2), but this isn't always
- * possible. For example, a Boolean reloption cannot be given an
- * out-of-range default, so we need another way to discover the source of
- * its value. This offset is only used if given a value greater than
- * zero.
- */
- int isset_offset;
} relopt_parse_elt;
/* Local reloption definition */
@@ -195,6 +190,8 @@ typedef struct local_relopts
extern relopt_kind add_reloption_kind(void);
extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc,
bool default_val, LOCKMODE lockmode);
+extern void add_trenary_reloption(bits32 kinds, const char *name,
+ const char *desc, int default_val, LOCKMODE lockmode);
extern void add_int_reloption(bits32 kinds, const char *name, const char *desc,
int default_val, int min_val, int max_val,
LOCKMODE lockmode);
@@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts,
extern void add_local_bool_reloption(local_relopts *relopts, const char *name,
const char *desc, bool default_val,
int offset);
+extern void add_local_trenary_reloption(local_relopts *relopts,
+ const char *name, const char *desc,
+ trenary default_val, int offset);
extern void add_local_int_reloption(local_relopts *relopts, const char *name,
const char *desc, int default_val,
int min_val, int max_val, int offset);
diff --git a/src/include/c.h b/src/include/c.h
index 39022f8a9dd..dec390c1e77 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void);
#include <stdbool.h>
+/*
+ * trenary
+ * Boolean value with an extrea "unset" option
+ *
+ * Trenary data type is used in relation options that can be "true", "false" or
+ * "unset". Since relation options are used deep inside the PostgreSQL code,
+ * this type is declared globally.
+*/
+
+typedef enum trenary
+{
+ TRENARY_FALSE = 0,
+ TRENARY_TRUE = 1,
+ TRENARY_UNSET = -1
+} trenary;
+
/* ----------------------------------------------------------------
* Section 3: standard system types
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b552359915f..08f93bde007 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -346,8 +346,7 @@ typedef struct StdRdOptions
bool user_catalog_table; /* use as an additional catalog relation */
int parallel_workers; /* max number of parallel workers */
StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */
- bool vacuum_truncate; /* enables vacuum to truncate a relation */
- bool vacuum_truncate_set; /* whether vacuum_truncate is set */
+ trenary vacuum_truncate; /* enables vacuum to truncate a relation */
/*
* Fraction of pages in a relation that vacuum can eagerly scan and fail
--
2.39.2
--nextPart6784952.tM3a2QDmDi
Content-Disposition: attachment;
filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="unicode-2-0-utf-8";
name="v1-0003-Add-alias-to-be-used-as-unset-state.patch"
^ permalink raw reply [nested|flat] 274+ messages in thread
end of thread, other threads:[~2025-08-29 14:12 UTC | newest]
Thread overview: 274+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2010-10-02 11:36 missing tags Andrew Dunstan <[email protected]>
2010-10-02 12:02 ` Magnus Hagander <[email protected]>
2010-10-02 14:08 ` Tom Lane <[email protected]>
2010-10-02 14:45 ` Magnus Hagander <[email protected]>
2010-10-02 15:09 ` Andrew Dunstan <[email protected]>
2010-10-02 18:48 ` Marc G. Fournier <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox