agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH] Add section headers to index types doc 269+ messages / 2 participants [nested] [flat]
* [PATCH] Add section headers to index types doc @ 2020-07-31 09:20 Dagfinn Ilmari MannsÃ¥ker <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Dagfinn Ilmari MannsÃ¥ker @ 2020-07-31 09:20 UTC (permalink / raw) This makes it easier to compare the properties of different index types at a glance. --- doc/src/sgml/indices.sgml | 460 ++++++++++++++++++++------------------ 1 file changed, 242 insertions(+), 218 deletions(-) diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 28adaba72d..332d161547 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -122,244 +122,268 @@ B-tree indexes, which fit the most common situations. </para> - <para> - <indexterm> - <primary>index</primary> - <secondary>B-tree</secondary> - </indexterm> - <indexterm> - <primary>B-tree</primary> - <see>index</see> - </indexterm> - B-trees can handle equality and range queries on data that can be sorted - into some ordering. - In particular, the <productname>PostgreSQL</productname> query planner - will consider using a B-tree index whenever an indexed column is - involved in a comparison using one of these operators: + <sect2 id="indexes-types-btree"> + <title>B-tree</title> - <simplelist> - <member><literal><</literal></member> - <member><literal><=</literal></member> - <member><literal>=</literal></member> - <member><literal>>=</literal></member> - <member><literal>></literal></member> - </simplelist> + <para> + <indexterm> + <primary>index</primary> + <secondary>B-tree</secondary> + </indexterm> + <indexterm> + <primary>B-tree</primary> + <see>index</see> + </indexterm> + B-trees can handle equality and range queries on data that can be sorted + into some ordering. + In particular, the <productname>PostgreSQL</productname> query planner + will consider using a B-tree index whenever an indexed column is + involved in a comparison using one of these operators: - Constructs equivalent to combinations of these operators, such as - <literal>BETWEEN</literal> and <literal>IN</literal>, can also be implemented with - a B-tree index search. Also, an <literal>IS NULL</literal> or <literal>IS NOT - NULL</literal> condition on an index column can be used with a B-tree index. - </para> + <simplelist> + <member><literal><</literal></member> + <member><literal><=</literal></member> + <member><literal>=</literal></member> + <member><literal>>=</literal></member> + <member><literal>></literal></member> + </simplelist> - <para> - The optimizer can also use a B-tree index for queries involving the - pattern matching operators <literal>LIKE</literal> and <literal>~</literal> - <emphasis>if</emphasis> the pattern is a constant and is anchored to - the beginning of the string — for example, <literal>col LIKE - 'foo%'</literal> or <literal>col ~ '^foo'</literal>, but not - <literal>col LIKE '%bar'</literal>. However, if your database does not - use the C locale you will need to create the index with a special - operator class to support indexing of pattern-matching queries; see - <xref linkend="indexes-opclass"/> below. It is also possible to use - B-tree indexes for <literal>ILIKE</literal> and - <literal>~*</literal>, but only if the pattern starts with - non-alphabetic characters, i.e., characters that are not affected by - upper/lower case conversion. - </para> + Constructs equivalent to combinations of these operators, such as + <literal>BETWEEN</literal> and <literal>IN</literal>, can also be implemented with + a B-tree index search. Also, an <literal>IS NULL</literal> or <literal>IS NOT + NULL</literal> condition on an index column can be used with a B-tree index. + </para> - <para> - B-tree indexes can also be used to retrieve data in sorted order. - This is not always faster than a simple scan and sort, but it is - often helpful. - </para> + <para> + The optimizer can also use a B-tree index for queries involving the + pattern matching operators <literal>LIKE</literal> and <literal>~</literal> + <emphasis>if</emphasis> the pattern is a constant and is anchored to + the beginning of the string — for example, <literal>col LIKE + 'foo%'</literal> or <literal>col ~ '^foo'</literal>, but not + <literal>col LIKE '%bar'</literal>. However, if your database does not + use the C locale you will need to create the index with a special + operator class to support indexing of pattern-matching queries; see + <xref linkend="indexes-opclass"/> below. It is also possible to use + B-tree indexes for <literal>ILIKE</literal> and + <literal>~*</literal>, but only if the pattern starts with + non-alphabetic characters, i.e., characters that are not affected by + upper/lower case conversion. + </para> - <para> - <indexterm> - <primary>index</primary> - <secondary>hash</secondary> - </indexterm> - <indexterm> - <primary>hash</primary> - <see>index</see> - </indexterm> - Hash indexes can only handle simple equality comparisons. - The query planner will consider using a hash index whenever an - indexed column is involved in a comparison using the - <literal>=</literal> operator. - The following command is used to create a hash index: -<synopsis> -CREATE INDEX <replaceable>name</replaceable> ON <replaceable>table</replaceable> USING HASH (<replaceable>column</replaceable>); -</synopsis> - </para> + <para> + B-tree indexes can also be used to retrieve data in sorted order. + This is not always faster than a simple scan and sort, but it is + often helpful. + </para> + </sect2> - <para> - <indexterm> - <primary>index</primary> - <secondary>GiST</secondary> - </indexterm> - <indexterm> - <primary>GiST</primary> - <see>index</see> - </indexterm> - GiST indexes are not a single kind of index, but rather an infrastructure - within which many different indexing strategies can be implemented. - Accordingly, the particular operators with which a GiST index can be - used vary depending on the indexing strategy (the <firstterm>operator - class</firstterm>). As an example, the standard distribution of - <productname>PostgreSQL</productname> includes GiST operator classes - for several two-dimensional geometric data types, which support indexed - queries using these operators: + <sect2 id="indexes-types-hash"> + <title>Hash</title> - <simplelist> - <member><literal><<</literal></member> - <member><literal>&<</literal></member> - <member><literal>&></literal></member> - <member><literal>>></literal></member> - <member><literal><<|</literal></member> - <member><literal>&<|</literal></member> - <member><literal>|&></literal></member> - <member><literal>|>></literal></member> - <member><literal>@></literal></member> - <member><literal><@</literal></member> - <member><literal>~=</literal></member> - <member><literal>&&</literal></member> - </simplelist> + <para> + <indexterm> + <primary>index</primary> + <secondary>hash</secondary> + </indexterm> + <indexterm> + <primary>hash</primary> + <see>index</see> + </indexterm> + Hash indexes can only handle simple equality comparisons. + The query planner will consider using a hash index whenever an + indexed column is involved in a comparison using the + <literal>=</literal> operator. + The following command is used to create a hash index: + <synopsis> + CREATE INDEX <replaceable>name</replaceable> ON <replaceable>table</replaceable> USING HASH (<replaceable>column</replaceable>); + </synopsis> + </para> + </sect2> - (See <xref linkend="functions-geometry"/> for the meaning of - these operators.) - The GiST operator classes included in the standard distribution are - documented in <xref linkend="gist-builtin-opclasses-table"/>. - Many other GiST operator - classes are available in the <literal>contrib</literal> collection or as separate - projects. For more information see <xref linkend="gist"/>. - </para> + <sect2 id="indexes-type-gist"> + <title>GiST</title> - <para> - GiST indexes are also capable of optimizing <quote>nearest-neighbor</quote> - searches, such as -<programlisting><![CDATA[ -SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; -]]> -</programlisting> - which finds the ten places closest to a given target point. The ability - to do this is again dependent on the particular operator class being used. - In <xref linkend="gist-builtin-opclasses-table"/>, operators that can be - used in this way are listed in the column <quote>Ordering Operators</quote>. - </para> + <para> + <indexterm> + <primary>index</primary> + <secondary>GiST</secondary> + </indexterm> + <indexterm> + <primary>GiST</primary> + <see>index</see> + </indexterm> + GiST indexes are not a single kind of index, but rather an infrastructure + within which many different indexing strategies can be implemented. + Accordingly, the particular operators with which a GiST index can be + used vary depending on the indexing strategy (the <firstterm>operator + class</firstterm>). As an example, the standard distribution of + <productname>PostgreSQL</productname> includes GiST operator classes + for several two-dimensional geometric data types, which support indexed + queries using these operators: - <para> - <indexterm> - <primary>index</primary> - <secondary>SP-GiST</secondary> - </indexterm> - <indexterm> - <primary>SP-GiST</primary> - <see>index</see> - </indexterm> - SP-GiST indexes, like GiST indexes, offer an infrastructure that supports - various kinds of searches. SP-GiST permits implementation of a wide range - of different non-balanced disk-based data structures, such as quadtrees, - k-d trees, and radix trees (tries). As an example, the standard distribution of - <productname>PostgreSQL</productname> includes SP-GiST operator classes - for two-dimensional points, which support indexed - queries using these operators: + <simplelist> + <member><literal><<</literal></member> + <member><literal>&<</literal></member> + <member><literal>&></literal></member> + <member><literal>>></literal></member> + <member><literal><<|</literal></member> + <member><literal>&<|</literal></member> + <member><literal>|&></literal></member> + <member><literal>|>></literal></member> + <member><literal>@></literal></member> + <member><literal><@</literal></member> + <member><literal>~=</literal></member> + <member><literal>&&</literal></member> + </simplelist> - <simplelist> - <member><literal><<</literal></member> - <member><literal>>></literal></member> - <member><literal>~=</literal></member> - <member><literal><@</literal></member> - <member><literal><^</literal></member> - <member><literal>>^</literal></member> - </simplelist> + (See <xref linkend="functions-geometry"/> for the meaning of + these operators.) + The GiST operator classes included in the standard distribution are + documented in <xref linkend="gist-builtin-opclasses-table"/>. + Many other GiST operator + classes are available in the <literal>contrib</literal> collection or as separate + projects. For more information see <xref linkend="gist"/>. + </para> - (See <xref linkend="functions-geometry"/> for the meaning of - these operators.) - The SP-GiST operator classes included in the standard distribution are - documented in <xref linkend="spgist-builtin-opclasses-table"/>. - For more information see <xref linkend="spgist"/>. - </para> + <para> + GiST indexes are also capable of optimizing <quote>nearest-neighbor</quote> + searches, such as + <programlisting><![CDATA[ + SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; + ]]> + </programlisting> + which finds the ten places closest to a given target point. The ability + to do this is again dependent on the particular operator class being used. + In <xref linkend="gist-builtin-opclasses-table"/>, operators that can be + used in this way are listed in the column <quote>Ordering Operators</quote>. + </para> + </sect2> - <para> - Like GiST, SP-GiST supports <quote>nearest-neighbor</quote> searches. - For SP-GiST operator classes that support distance ordering, the - corresponding operator is specified in the <quote>Ordering Operators</quote> - column in <xref linkend="spgist-builtin-opclasses-table"/>. - </para> + <sect2 id="indexes-type-spgist"> + <title>SP-GiST</title> - <para> - <indexterm> - <primary>index</primary> - <secondary>GIN</secondary> - </indexterm> - <indexterm> - <primary>GIN</primary> - <see>index</see> - </indexterm> - GIN indexes are <quote>inverted indexes</quote> which are appropriate for - data values that contain multiple component values, such as arrays. An - inverted index contains a separate entry for each component value, and - can efficiently handle queries that test for the presence of specific - component values. - </para> + <para> + <indexterm> + <primary>index</primary> + <secondary>SP-GiST</secondary> + </indexterm> + <indexterm> + <primary>SP-GiST</primary> + <see>index</see> + </indexterm> + SP-GiST indexes, like GiST indexes, offer an infrastructure that supports + various kinds of searches. SP-GiST permits implementation of a wide range + of different non-balanced disk-based data structures, such as quadtrees, + k-d trees, and radix trees (tries). As an example, the standard distribution of + <productname>PostgreSQL</productname> includes SP-GiST operator classes + for two-dimensional points, which support indexed + queries using these operators: - <para> - Like GiST and SP-GiST, GIN can support - many different user-defined indexing strategies, and the particular - operators with which a GIN index can be used vary depending on the - indexing strategy. - As an example, the standard distribution of - <productname>PostgreSQL</productname> includes a GIN operator class - for arrays, which supports indexed queries using these operators: + <simplelist> + <member><literal><<</literal></member> + <member><literal>>></literal></member> + <member><literal>~=</literal></member> + <member><literal><@</literal></member> + <member><literal><^</literal></member> + <member><literal>>^</literal></member> + </simplelist> - <simplelist> - <member><literal><@</literal></member> - <member><literal>@></literal></member> - <member><literal>=</literal></member> - <member><literal>&&</literal></member> - </simplelist> + (See <xref linkend="functions-geometry"/> for the meaning of + these operators.) + The SP-GiST operator classes included in the standard distribution are + documented in <xref linkend="spgist-builtin-opclasses-table"/>. + For more information see <xref linkend="spgist"/>. + </para> - (See <xref linkend="functions-array"/> for the meaning of - these operators.) - The GIN operator classes included in the standard distribution are - documented in <xref linkend="gin-builtin-opclasses-table"/>. - Many other GIN operator - classes are available in the <literal>contrib</literal> collection or as separate - projects. For more information see <xref linkend="gin"/>. - </para> + <para> + Like GiST, SP-GiST supports <quote>nearest-neighbor</quote> searches. + For SP-GiST operator classes that support distance ordering, the + corresponding operator is specified in the <quote>Ordering Operators</quote> + column in <xref linkend="spgist-builtin-opclasses-table"/>. + </para> + </sect2> - <para> - <indexterm> - <primary>index</primary> - <secondary>BRIN</secondary> - </indexterm> - <indexterm> - <primary>BRIN</primary> - <see>index</see> - </indexterm> - BRIN indexes (a shorthand for Block Range INdexes) store summaries about - the values stored in consecutive physical block ranges of a table. - Like GiST, SP-GiST and GIN, - BRIN can support many different indexing strategies, - and the particular operators with which a BRIN index can be used - vary depending on the indexing strategy. - For data types that have a linear sort order, the indexed data - corresponds to the minimum and maximum values of the - values in the column for each block range. This supports indexed queries - using these operators: + <sect2 id="indexes-types-gin"> + <title>GIN</title> - <simplelist> - <member><literal><</literal></member> - <member><literal><=</literal></member> - <member><literal>=</literal></member> - <member><literal>>=</literal></member> - <member><literal>></literal></member> - </simplelist> + <para> + <indexterm> + <primary>index</primary> + <secondary>GIN</secondary> + </indexterm> + <indexterm> + <primary>GIN</primary> + <see>index</see> + </indexterm> + GIN indexes are <quote>inverted indexes</quote> which are appropriate for + data values that contain multiple component values, such as arrays. An + inverted index contains a separate entry for each component value, and + can efficiently handle queries that test for the presence of specific + component values. + </para> - The BRIN operator classes included in the standard distribution are - documented in <xref linkend="brin-builtin-opclasses-table"/>. - For more information see <xref linkend="brin"/>. - </para> + <para> + Like GiST and SP-GiST, GIN can support + many different user-defined indexing strategies, and the particular + operators with which a GIN index can be used vary depending on the + indexing strategy. + As an example, the standard distribution of + <productname>PostgreSQL</productname> includes a GIN operator class + for arrays, which supports indexed queries using these operators: + + <simplelist> + <member><literal><@</literal></member> + <member><literal>@></literal></member> + <member><literal>=</literal></member> + <member><literal>&&</literal></member> + </simplelist> + + (See <xref linkend="functions-array"/> for the meaning of + these operators.) + The GIN operator classes included in the standard distribution are + documented in <xref linkend="gin-builtin-opclasses-table"/>. + Many other GIN operator + classes are available in the <literal>contrib</literal> collection or as separate + projects. For more information see <xref linkend="gin"/>. + </para> + </sect2> + + <sect2 id="indexes-types-brin"> + <title>BRIN</title> + + <para> + <indexterm> + <primary>index</primary> + <secondary>BRIN</secondary> + </indexterm> + <indexterm> + <primary>BRIN</primary> + <see>index</see> + </indexterm> + BRIN indexes (a shorthand for Block Range INdexes) store summaries about + the values stored in consecutive physical block ranges of a table. + Like GiST, SP-GiST and GIN, + BRIN can support many different indexing strategies, + and the particular operators with which a BRIN index can be used + vary depending on the indexing strategy. + For data types that have a linear sort order, the indexed data + corresponds to the minimum and maximum values of the + values in the column for each block range. This supports indexed queries + using these operators: + + <simplelist> + <member><literal><</literal></member> + <member><literal><=</literal></member> + <member><literal>=</literal></member> + <member><literal>>=</literal></member> + <member><literal>></literal></member> + </simplelist> + + The BRIN operator classes included in the standard distribution are + documented in <xref linkend="brin-builtin-opclasses-table"/>. + For more information see <xref linkend="brin"/>. + </para> + </sect2> </sect1> -- 2.27.0 --=-=-=-- ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
end of thread, other threads:[~2025-08-29 14:12 UTC | newest] Thread overview: 269+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2020-07-31 09:20 [PATCH] Add section headers to index types doc Dagfinn Ilmari Mannsåker <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/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